package org.mentalsmash.crossroads;

import java.awt.Point;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.List;

import org.apache.log4j.Logger;
import org.mentalsmash.crossroads.Crossword.CrosswordEntry;

/**
 * Exports a Crossword instance to a prolog source
 * @author Andrea Reale
 *
 */
public class PrologExporter implements CrosswordExporter<String> {

	private static Logger log = Logger.getLogger(PrologExporter.class);
	private DictionarySource _dictionarySource;
	private static String SOLUTION_PREDICATE = "crossword_solution";
	
	public PrologExporter(DictionarySource d){
		setDictionarySource(d);
	}
	
	
	/**
	 * @param dictionarySource an instance of a DictionarySource for the exporter
	 */
	public void setDictionarySource(DictionarySource dictionarySource) {
		if(dictionarySource == null)
			throw new IllegalArgumentException("Invalid dictionary source");
		_dictionarySource = dictionarySource;
	}

	/**
	 * 
	 * @return the dictionary source used by this exporter
	 */
	public DictionarySource getDictionarySource() {
		return _dictionarySource;
	}
	
	
	public String getMainRuleHead(Crossword toSolve){
		//Taking all the entries for the crossword
		List<CrosswordEntry> across = toSolve.getOrderedAcrossEntries();
		List<CrosswordEntry> down = toSolve.getOrderedDownEntries();
		
		return genRuleHead(across, down);
	}
	
	
	@Override
	public String toExternalRepresentation(Crossword input) {
		ByteArrayOutputStream outputString = new ByteArrayOutputStream();
		buildSolverTheory(input,outputString);
		String built = null;
		try{
			built = new String(outputString.toByteArray(),Configuration.getInstance().getEncoding());
		} catch (UnsupportedEncodingException e ){
			log.fatal("Unsupported encoding while writing svg: " + Configuration.getInstance().getEncoding());
			e.printStackTrace();
			System.exit(-1);
		} finally {
			try{ 
			 outputString.close();
			} catch (IOException e) {
				log.warn("While closing SVG outputstream: "+ e.getMessage()); 
			}
		}
		
		return built;
	}
	
	
	/**
	 * Builds the prolog theory used to solve the crossword
	 * @param toSolve the crossword instance to solve
	 * @param out the output stream where to write the solver theory
	 */
	public void buildSolverTheory(Crossword toSolve,OutputStream out){
		
		PrintStream prologOut = new PrintStream(out);
		// I have to build the prolog rule
		// crossword_solution([Sol-1, ... , Sol-n]) where Sol-i is the atom representing 
		// the solution for the i-th entry (across and down entry are not excplictly distinguisched)
		
		// for each I build a dictionary of words of the desired length using a DictionarySource
		// and I create a prolog term dictionary(entry_id,[word-1, ... , word-n]) being word-[1-n] the words in the dictionary
		
		// the crossword_solution([Sol-1, ... , Sol-n]) will represent a valid crossword solution if for each entry-i:
		// Sol-i is a member of the dictionary for entry-i, AND, the atom_chars(Sol-i,ListOfChars) are respecting the constraints
		// of the crossword.
		
		//Taking all the entries for the crossword
		List<CrosswordEntry> across = toSolve.getOrderedAcrossEntries();
		List<CrosswordEntry> down = toSolve.getOrderedDownEntries();
		
		log.debug("Building crossword solving theory...");
		
		
		//builds the rule crossword_solution(]Sol-1, ... , Sol-n]) :- ...
		String mainRule = buildMainRule(across, down);
		
		String dictionaries = buildDictRules(across,down);
		
		prologOut.println(mainRule + dictionaries);
		log.debug("Done.\n" + mainRule + dictionaries);
		
		
	}

	/**
	 * Builds the rules that defines the dictinaries for words
	 * @param across the list of across entries
	 * @param down the list of down entries
	 * @return a stirng containing prolog clauses describing dictionaries
	 */
	private String buildDictRules(List<CrosswordEntry> across, List<CrosswordEntry> down) {
		
		StringBuilder dictRules = new StringBuilder();
		dictRules.append("\n\n");
		
		for(CrosswordEntry entry : across){
			String definition = entry.getDefinition();
			int solutionLength = entry.length();
		    List<String> tmpList =_dictionarySource.getDictionaryForDefinition(definition, solutionLength);
			String[] dict = tmpList.toArray(new String[tmpList.size()]);
			dictRules.append("dictionary(e_"+entry.getId().toLowerCase()+", " + buildDefList(dict) + " ). \n");
		}
		
		for(CrosswordEntry entry : down){
			String definition = entry.getDefinition();
			int solutionLength = entry.length();
		    List<String> tmpList =_dictionarySource.getDictionaryForDefinition(definition, solutionLength);
			String[] dict = tmpList.toArray(new String[tmpList.size()]);
			dictRules.append("dictionary(e_"+entry.getId().toLowerCase()+", " + buildDefList(dict) + " ). \n");
		}
		
		return dictRules.toString();
	}



	/**
	 * builds the main crossword solving rule, i.e. crossword_solutions(Sol-1 , ... , Sol - N) :- ...
	 * @param across a list of the across entries of the cwd
	 * @param down a list of the down entries of the cwd
	 * @return a string containing the main prolog rule as described above
	 */
	private String buildMainRule(List<Crossword.CrosswordEntry> across,
			List<Crossword.CrosswordEntry> down) {
		
		StringBuilder crosswordSolutionRule = new StringBuilder();
			crosswordSolutionRule.append(genRuleHead(across, down));
		
		int entryCounter=0;
		//for each entry I'm creating the appropriete clauses for the body of the crossword_solution/N rule
		for(Crossword.CrosswordEntry entry : across){
			crosswordSolutionRule.append("\t");
			crosswordSolutionRule.append("dictionary(e_" + entry.getId().toLowerCase() + ", DIC_" + entryCounter + ") , " );
			crosswordSolutionRule.append("member(E_" + entry.getId().toLowerCase() + " , DIC_" +entryCounter + ") , " );
			crosswordSolutionRule.append("atom_chars(E_"+ entry.getId().toLowerCase() + " , " + buildChList(entry) + ") , ");
			crosswordSolutionRule.append("\n");
			entryCounter++;
		}
		for(int i = 0; i < down.size()-1;i++){
			Crossword.CrosswordEntry entry = down.get(i);
			crosswordSolutionRule.append("\t");
			crosswordSolutionRule.append("dictionary(e_" + entry.getId().toLowerCase() + ", DIC_" + entryCounter + ") , " );
			crosswordSolutionRule.append("member(E_" + entry.getId().toLowerCase() + " , DIC_" +entryCounter + ") , " );
			crosswordSolutionRule.append("atom_chars(E_"+ entry.getId().toLowerCase() + " , " + buildChList(entry) + ") , ");
			crosswordSolutionRule.append("\n");
			entryCounter++;
		}
		
		if(down.size() > 0){

			//the last arg is not followed by a comma
			CrosswordEntry last =  down.get(down.size()-1);
			crosswordSolutionRule.append("\t");
			crosswordSolutionRule.append("dictionary(e_" + last.getId().toLowerCase() + ", DIC_" + entryCounter + ") , " );
			crosswordSolutionRule.append("member(E_" + last.getId().toLowerCase() + " , DIC_" +entryCounter + ") , " );
			crosswordSolutionRule.append("atom_chars(E_"+ last.getId().toLowerCase() + " , " + buildChList(last) + ") .");
		}
		crosswordSolutionRule.append("\n");
		
		return crosswordSolutionRule.toString();
	}

	/**
	 * Generates the head of the crossword_solution/N rule, wich is then used both to generate the rule and the query
	 * @param across a list of the across entries of the cwd
	 * @param down a list of the down entries of the cwd
	 * @return the head of the crossword_solution/N rule
	 */
	private String genRuleHead(List<Crossword.CrosswordEntry> across,
			List<Crossword.CrosswordEntry> down) {
		
		StringBuilder ruleHead = new StringBuilder();
		ruleHead.append(SOLUTION_PREDICATE + "(");
		//for each entry I'm appending a prolog var as an argument of the prolog rule
		for(Crossword.CrosswordEntry entry : across){
			ruleHead.append("E_"+entry.getId().toLowerCase()+ " , ");
		}
		for(int i = 0; i < down.size()-1;i++){
			Crossword.CrosswordEntry entry = down.get(i);
			ruleHead.append("E_"+entry.getId().toLowerCase()+ " , ");
		}
		
		if(down.size() > 0){//in case the list is empty
			//the last arg is not followed by a comma
			ruleHead.append("E_"+down.get(down.size()-1).getId().toLowerCase());
			ruleHead.append(") :- \n");
		}
		return ruleHead.toString();
	}
	
	/**
	 * generates a list of prolog variables representing crossword characters. Each generated Var has the form P_X_Y where X and Y
	 * are the coordinates of the char in the crossword matrix
	 * @param entry an entry in the crossword
	 * @return read above, please :)
	 */
	private String buildChList(CrosswordEntry entry) {
		if(entry == null)
			throw new IllegalArgumentException("Cell cannot be null");
		Point[] cells = entry.getCells();
		String[] strings = new String[cells.length];
		for(int i=0 ; i<cells.length;i++){
			strings[i] = "P_" + cells[i].x + "_" + cells[i].y;
		}

		
		
		return buildPlList(strings);
	}
	
	/**
	 * generates a string describing a prolog list whose elements are the elements in the string array
	 */
	private String buildPlList(String[] elements){
		if(elements == null)
			throw new IllegalArgumentException("Cell cannot be null");
		StringBuilder list = new StringBuilder();
		list.append("[ ");
		for(int i=0; i< elements.length-1;i++)
			list.append(elements[i] + ", ");
		if(elements.length > 0)
			list.append(elements[elements.length-1]);
		list.append(" ]");
		
		return list.toString();
		
	}
	
	/**
	 * This method is simply used to add single quotes to defintions strings wich may contain special characters but
	 * are to be interpreted as prolog atoms.
	 */
	private String buildDefList(String[] dict) {
		for(int i = 0; i < dict.length; i++){
			dict[i] = "'" + dict[i] + "'";
		}
		return buildPlList(dict);
	}


}
